package net.fornwall.eclipsecoder.javasupport;

import java.lang.reflect.Array;

import net.fornwall.eclipsecoder.languages.LanguageSupport;
import net.fornwall.eclipsecoder.stats.CodeGenerator;
import net.fornwall.eclipsecoder.stats.ProblemStatement;

public class JavaCodeGenerator extends CodeGenerator {

    private final static String AS_INTEGERS = "    public static Integer[] asIntegers(int[] array) {\n"
            + "        Integer[] result = new Integer[array.length];\n"
            + "        for (int i = 0; i < result.length; i++) {\n"
            + "            result[i] = new Integer(array[i]);\n"
            + "        }\n"
            + "        return result;\n" + "    }\n\n";

    private final static String COMPARE_ARRAYS = "    public static void assertEquals(Object[] expected, Object[] actual) {\n"
            + "        assertEquals(Arrays.asList(expected), Arrays.asList(actual));\n"
            + "    }\n\n";

    private final static String COMPARE_DOUBLE_ARRAYS = "    public static void assertEquals(double[] expected, double[] actual) {\n"
            + "        boolean failed = expected.length != actual.length;\n"
            + "        for (int i = 0; i < expected.length && !failed; i++) {\n"
            + "            if (Double.isNaN(expected[i]) && !Double.isNaN(actual[i])) {\n"
            + "                failed = true;\n"
            + "            } else {\n"
            + "                double delta = Math.max(1e-9, 1e-9 * Math.abs(expected[i]));\n"
            + "                failed = (Math.abs(actual[i] - expected[i]) > delta);\n"
            + "            }\n"
            + "        }\n"
            + "        if (failed) {\n"
            + "            List expectedList = new ArrayList();\n"
            + "            for (int i = 0; i < expected.length; i++) expectedList.add(new Double(expected[i]));\n"
            + "            List actualList = new ArrayList();\n"
            + "            for (int i = 0; i < actual.length; i++) actualList.add(new Double(actual[i]));\n"
            + "            fail(\"expected:<\" + expectedList + \">, but was:<\" + actualList + \">\");\n"
            + "        }\n" + "    }\n\n";

    private final static String COMPARE_DOUBLES = "    public static void assertEquals(double expected, double actual) {\n"
            + "        if (Double.isNaN(expected)) {\n"
            + "            assertTrue(\"expected: <NaN> but was: <\" + actual + \">\", Double.isNaN(actual));\n"
            + "            return;\n"
            + "        }\n"
            + "        double delta = Math.max(1e-9, 1e-9 * Math.abs(expected));\n"
            + "        assertEquals(expected, actual, delta);\n" + "    }\n\n";

    private final static String COMPARE_INT_ARRAYS = "    public static void assertEquals(int[] expected, int[] actual) {\n"
            + "        assertEquals(asIntegers(expected), asIntegers(actual));\n" + "    }\n\n";

    public static final String DEFAULT_CODE_TEMPLATE = "import java.util.*;\n\npublic class "
            + LanguageSupport.CLASSNAME_TAG + " {\n\n" + "    public "
            + LanguageSupport.RETURNTYPE_TAG + " " + LanguageSupport.METHODNAME_TAG + "("
            + LanguageSupport.METHODPARAMS_TAG + ") {\n" + "        return "
            + LanguageSupport.DUMMYRETURN_TAG + ";\n" + "    }\n\n" + "}\n";

    public JavaCodeGenerator(ProblemStatement problemStatement) {
        super(problemStatement);
    }

    private String getCreateStatement(Object value, String variableName) {
        Class<?> type = value.getClass();
        if (type.isArray()) {
            StringBuilder builder = new StringBuilder(getTypeString(type) + " " + variableName
                    + " = new " + getTypeString(type) + "{");
            for (int i = 0; i < Array.getLength(value); i++) {
                if (i != 0) {
                    builder.append(", ");
                }
                builder.append(getSimpleCreateStatement(Array.get(value, i)));
            }
            builder.append("};\n");
            return builder.toString();
        }

		return getTypeString(type) + " " + variableName + " = "
		        + getSimpleCreateStatement(value) + ";\n";
    }

    @Override
    public String getDummyReturnString() {
        if (problemStatement.getReturnType() == Integer.class
                || problemStatement.getReturnType() == Long.class
                || problemStatement.getReturnType() == Character.class) {
            return "0";
        } else if (problemStatement.getReturnType() == Double.class) {
            return "0.0";
        } else {
            return "null";
        }
    }

    /** One-dimensional */
    private String getSimpleCreateStatement(Object value) {
        if (value.getClass() == Character.class) {
            return "'" + value + "'";
        } else if (value.getClass() == String.class) {
            return '"' + value.toString().replaceAll("\\\\", "\\\\\\\\") + '"';
        } else {
            return value.toString() + ((value instanceof Long) ? "L" : "");
        }
    }

    @Override
    public String getTestsSource() {
        boolean multiDimensional = problemStatement.getReturnType().isArray();
        boolean intArray = multiDimensional
                && problemStatement.getReturnType().getSimpleName().startsWith("Integer");
        boolean doubleReturn = problemStatement.getReturnType() == Double.class;

        StringBuilder result = new StringBuilder();
        result.append("import junit.framework.TestCase;\n");
        if (multiDimensional) {
            result.append("import java.util.Arrays;\n");
            if (doubleReturn) {
                result.append("import java.util.ArrayList;\n");
                result.append("import java.util.List;\n");
            }
        }
        result.append("\npublic class " + problemStatement.getSolutionClassName()
                + "Test extends TestCase {\n\n" + "    protected "
                + problemStatement.getSolutionClassName() + " solution;\n\n"
                + "    protected void setUp() {\n" + "        solution = new "
                + problemStatement.getSolutionClassName() + "();\n" + "    }\n\n");

        if (doubleReturn && !multiDimensional) {
            result.append(COMPARE_DOUBLES);
        }

        if (multiDimensional) {
            result.append(COMPARE_ARRAYS);
            if (intArray) {
                result.append(AS_INTEGERS);
                result.append(COMPARE_INT_ARRAYS);
            }

            if (doubleReturn) {
                result.append(COMPARE_DOUBLE_ARRAYS);
            }
        }

        int i = -1;
        for (ProblemStatement.TestCase testCase : problemStatement.getTestCases()) {
            i++;
            result.append("    public void testCase" + i + "() {\n");

            for (int param = 0; param < problemStatement.getParameterNames().size(); param++) {
                result.append("        "
                        + getCreateStatement(testCase.parameters[param], problemStatement
                                .getParameterNames().get(param)));
            }

            result.append("\n        " + getCreateStatement(testCase.getReturnValue(), "expected"));

            result.append("        " + getTypeString(problemStatement.getReturnType())
                    + " actual = solution." + problemStatement.getSolutionMethodName() + "(");
            for (int j = 0; j < problemStatement.getParameterNames().size(); j++) {
                result.append(problemStatement.getParameterNames().get(j));
                if (j != problemStatement.getParameterNames().size() - 1) {
                    result.append(", ");
                }
            }
            result.append(");\n");

            result.append("\n        assertEquals(expected, actual);\n    }\n\n");
        }

        result.append("}\n");
        return result.toString();
    }

    @Override
    public String getTypeString(Class<?> type) {
        if (type == Integer.class) {
            return "int";
        } else if (type == Character.class) {
            return "char";
        } else if (type == Double.class) {
            return "double";
        } else if (type == Long.class) {
            return "long";
        } else if (type.isArray()) {
            return getTypeString(type.getComponentType()) + "[]";
        } else {
            // String
            return type.getSimpleName();
        }
    }

}
